﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using Nextensions.Net.Protocols.Http;

namespace Nextensions.Net
{
    public class HTTP
    {
        public static HttpResponse Get(string URL)
        {
            return Get(URL, null);
        }

        /// <summary>
        /// Uses the default behavior of the HttpWebRequest object. Especially: AllowAutoRedirect = true.
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static HttpResponse Get2(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
            Stream responseStream = webResponse.GetResponseStream();
            StreamReader reader = new StreamReader(responseStream);

            HttpResponse response = new HttpResponse(url, reader.ReadToEnd(), webResponse.StatusCode);

            reader.Close();
            responseStream.Close();

            return response;
        }

        public static HttpResponse Get(string URL, List<string> Cookies)
        {
            return Get(URL, Cookies, null, null);
        }

        public static HttpResponse Get(string URL, List<string> Cookies, List<KeyValuePair<string,string>> Headers)
        {
            return Get(URL, Cookies, Headers, null);
        }

        public static HttpResponse Get(string URL, List<string> Cookies, Encoding Enc)
        {
            return Get(URL, Cookies, null, Enc);
        }

        public static HttpResponse Get(string url, List<string> cookies, List<KeyValuePair<string, string>> headers, Encoding encoding)
        {
            return AutoRedirectRequestResponse(new HttpRequest(url, cookies, headers, encoding));
            //return AutoRedirectRequestResponse(URL, null, null, RequestMethods.GET, Cookies, Headers, Enc, null, null, null);
        }

        public static HttpResponse Post(HttpRequest request)
        {
            if (request.Method != RequestMethods.POST)
                request.Method = RequestMethods.POST;

            return AutoRedirectRequestResponse(request);
        }
        
        public static HttpResponse Post(string URL, string PostData)
        {
            return Post(URL, PostData, new List<string>(0));
        }

        public static HttpResponse Post(string URL, string PostData, string UserName, string Password)
        {
            return Post(URL, PostData, null, UserName, Password);
        }

        public static HttpResponse Post(string URL, string PostData, List<string> Cookies)
        {
            return Post(URL, PostData, Cookies, DefaultEncoding);
        }

        public static HttpResponse Post(string URL, string PostData, List<string> Cookies, string UserName, string Password)
        {
            return Post(URL, PostData, Cookies, DefaultEncoding, UserName, Password);
        }

        public static HttpResponse Post(string url, string postData, List<string> cookies, Encoding encoding)
        {
            HttpRequest request = new HttpRequest(url, cookies, encoding);
            request.Method = RequestMethods.POST;
            request.Post = postData;
            return AutoRedirectRequestResponse(request);
        }

        public static HttpResponse Post(string URL, string PostData, List<string> Cookies, Encoding Enc, string UserName, string Password)
        {
            return Post(URL, PostData, Cookies, null, Enc, null, null, null);
        }

        public static HttpResponse Post(string URL, string PostData, List<string> Cookies, List<KeyValuePair<string, string>> Headers, Encoding Enc, string UserName, string Password, string Domain)
        {
            return Post(URL, PostData, null, Cookies, Headers, Enc, UserName, Password, Domain);
        }

        public static HttpResponse Post(string url, string postData, string contentType, List<string> cookies, List<KeyValuePair<string,string>> headers, Encoding encoding, string userName, string password, string domain)
        {
            return AutoRedirectRequestResponse(new HttpRequest(url, postData, contentType, RequestMethods.POST, cookies, headers, encoding, userName, password, domain));
        }

        private static HttpResponse AutoRedirectRequestResponse(HttpRequest request)
        {
            HttpResponse res;

            res = RequestResponse(request);

            while (res.Status == HttpStatusCode.Found || res.Status == HttpStatusCode.Redirect)
            {
                if (request.Cookies == null)
                    request.Cookies = new List<string>(res.Cookies);
                else
                    request.Cookies.AddRange(res.Cookies);

                if (!URL.IsAbsolute(res.RedirectURL))
                    request.Url = URL.SetRelative(request.Url, res.RedirectURL);
                else
                    request.Url = res.RedirectURL;

                request.Method = RequestMethods.GET;
                res = RequestResponse(request);
            }

            return res;
        }

        private static HttpResponse RequestResponse(HttpRequest request)
        {
            HttpResponse res;
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(request.Url);
            ServicePointManager.ServerCertificateValidationCallback += delegate { return true; };
            webRequest.AllowAutoRedirect = false;

            if (!String.IsNullOrEmpty(request.UserName))
            {
                Regex domainRe = new Regex(@"^(?<domain>.*)\\");
                Match domainMatch = domainRe.Match(request.UserName);

                if (domainMatch.Success)
                {
                    string domainTemp = domainMatch.Groups["domain"].Value;

                    if (!String.IsNullOrEmpty(request.Domain) && !String.IsNullOrEmpty(domainTemp))
                        throw new ArgumentException(String.Format("A domain was specified in the user name as well as explicitely in the domain argument. Please specify a domain in only one place. Domain in user name: '{0}'; Domain as argument: '{1}'", domainTemp, request.Domain));

                    request.UserName = domainRe.Replace(request.UserName, "");
                    request.Domain = domainTemp;
                }


                if (!String.IsNullOrEmpty(request.Domain))
                    webRequest.Credentials = new NetworkCredential(request.UserName, request.Password, request.Domain);
                else
                    webRequest.Credentials = new NetworkCredential(request.UserName, request.Password);
            }

            if (request.Cookies != null)
            {
                webRequest.Headers.Add(HttpRequestHeader.Cookie, String.Join(";", request.Cookies.ToArray()).Replace(";;", ";"));
            }

            if (request.Headers != null)
            {
                foreach (KeyValuePair<string, string> header in request.Headers)
                {
                    if (header.Key == "User-Agent")
                        webRequest.UserAgent = header.Value;
                    else
                        webRequest.Headers.Add(header.Key, header.Value);
                }
            }

            if (request.Encoding == null)
                request.Encoding = DefaultEncoding;

            webRequest.Method = request.Method.ToString();

            if (request.Method == RequestMethods.POST)
            {
                byte[] data = request.Encoding.GetBytes(request.Post);
                if (request.ContentType == null)
                    webRequest.ContentType = "application/x-www-form-urlencoded";
                else
                    webRequest.ContentType = request.ContentType;
                webRequest.ContentLength = data.Length;
                Stream stream = webRequest.GetRequestStream();
                stream.Write(data, 0, data.Length);
                stream.Close();
            }

            HttpWebResponse response;
            TimeSpan timeTaken;

            try
            {
                DateTime start = DateTime.UtcNow;
                response = (HttpWebResponse)webRequest.GetResponse();
                timeTaken = DateTime.UtcNow.Subtract(start);
            }
            catch
            {
                return new HttpResponse(request.Url, "", HttpStatusCode.ExpectationFailed);
            }
            
            Stream responseStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(responseStream, request.Encoding);

            res = 
                new HttpResponse(
                    request.Url,
                    reader.ReadToEnd(),
                    response.StatusCode,
                    response.Headers.GetValues("Set-Cookie"));

            reader.Close();
            responseStream.Close();
            response.Close();

            res.TimeTaken = timeTaken;

            if (response.StatusCode == HttpStatusCode.Found)
                res.RedirectURL = response.Headers.GetValues("Location")[0];

            return res;
        }

        public static string EditCookie(string Cookie)
        {
            string res = Regex.Replace(Cookie, @"path=/\S*;?", "");
            res = Regex.Replace(res, @"\s*\bHttpOnly\b\s*", "");
            return res;
        }

        protected static Encoding DefaultEncoding
        {
            get { return new ASCIIEncoding(); }
        }
    }
}
